using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;

namespace LightCAD.Three
{
    public class Frustum
    {
        #region scope properties or methods
        //private static Sphere _sphere = new Sphere();
        //private static Vector3 _vector = new Vector3();
        private FrustumContext getContext()
        {
            var tctx = ThreadContext.getCurrThreadContext();
            FrustumContext _context = tctx.FrustumCtx;
            return _context;
        }

        #endregion

        #region Properties



        public Plane[] planes;

        #endregion

        #region constructor
        public Frustum(Plane p0 = null, Plane p1 = null, Plane p2 = null, Plane p3 = null, Plane p4 = null, Plane p5 = null)
        {
            if (p0 == null) p0 = new Plane();
            if (p1 == null) p1 = new Plane();
            if (p2 == null) p2 = new Plane();
            if (p3 == null) p3 = new Plane();
            if (p4 == null) p4 = new Plane();
            if (p5 == null) p5 = new Plane();
            this.planes = new Plane[] { p0, p1, p2, p3, p4, p5 };
        }
        #endregion

        #region methods
        public Frustum set(Plane p0, Plane p1, Plane p2, Plane p3, Plane p4, Plane p5)
        {
            Plane[] planes = this.planes;
            planes[0].copy(p0);
            planes[1].copy(p1);
            planes[2].copy(p2);
            planes[3].copy(p3);
            planes[4].copy(p4);
            planes[5].copy(p5);
            return this;
        }
        public Frustum copy(Frustum frustum)
        {
            Plane[] planes = this.planes;
            for (int i = 0; i < 6; i++)
            {
                planes[i].copy(frustum.planes[i]);
            }
            return this;
        }
        public Frustum setFromProjectionMatrix(Matrix4 m)
        {
            Plane[] planes = this.planes;
            double[] me = m.elements;
            double me0 = me[0], me1 = me[1], me2 = me[2], me3 = me[3];
            double me4 = me[4], me5 = me[5], me6 = me[6], me7 = me[7];
            double me8 = me[8], me9 = me[9], me10 = me[10], me11 = me[11];
            double me12 = me[12], me13 = me[13], me14 = me[14], me15 = me[15];
            planes[0].setComponents(me3 - me0, me7 - me4, me11 - me8, me15 - me12).normalize();
            planes[1].setComponents(me3 + me0, me7 + me4, me11 + me8, me15 + me12).normalize();
            planes[2].setComponents(me3 + me1, me7 + me5, me11 + me9, me15 + me13).normalize();
            planes[3].setComponents(me3 - me1, me7 - me5, me11 - me9, me15 - me13).normalize();
            planes[4].setComponents(me3 - me2, me7 - me6, me11 - me10, me15 - me14).normalize();
            planes[5].setComponents(me3 + me2, me7 + me6, me11 + me10, me15 + me14).normalize();
            return this;
        }
        public bool intersectsObject(Object3D _object)
        {
            var ctx = getContext();
            if (_object == null)
                return false;

            if (_object is IGeometry)
            {
                var geometry = (_object as IGeometry).getGeometry();
                if (geometry.boundingSphere == null) geometry.computeBoundingSphere();
                ctx._sphere.copy(geometry.boundingSphere).applyMatrix4(_object.matrixWorld);
            }
            else if (_object is IBounding)
            {
                var sphere = (Sphere)_object.GetField("boundingSphere");
                if (sphere == null)
                {
                    (_object as IBounding).computeBoundingSphere();
                    sphere = (Sphere)_object.GetField("boundingSphere");
                }
                ctx._sphere.copy(sphere).applyMatrix4(_object.matrixWorld);
            }
            else
            {
                return false;
            }


            return this.intersectsSphere(ctx._sphere);
        }
        public bool intersectsSprite(Sprite sprite)
        {
            var _sphere = getContext()._sphere;
            _sphere.center.set(0, 0, 0);
            _sphere.radius = 0.7071067811865476;
            _sphere.applyMatrix4(sprite.matrixWorld);
            return this.intersectsSphere(_sphere);
        }
        public bool intersectsSphere(Sphere sphere)
        {
            Plane[] planes = this.planes;
            Vector3 center = sphere.center;
            double negRadius = -sphere.radius;
            for (int i = 0; i < 6; i++)
            {
                double distance = planes[i].distanceToPoint(center);
                if (distance < negRadius)
                {
                    return false;
                }
            }
            return true;
        }
        public bool intersectsBox(Box3 box)
        {
            var _vector = getContext()._vector;
            Plane[] planes = this.planes;
            for (int i = 0; i < 6; i++)
            {
                Plane plane = planes[i];
                // corner at max distance
                _vector.x = plane.normal.x > 0 ? box.max.x : box.min.x;
                _vector.y = plane.normal.y > 0 ? box.max.y : box.min.y;
                _vector.z = plane.normal.z > 0 ? box.max.z : box.min.z;
                if (plane.distanceToPoint(_vector) < 0)
                {
                    return false;
                }
            }
            return true;
        }
        public bool containsPoint(Vector3 point)
        {
            Plane[] planes = this.planes;
            for (int i = 0; i < 6; i++)
            {
                if (planes[i].distanceToPoint(point) < 0)
                {
                    return false;
                }
            }
            return true;
        }
        public Frustum clone()
        {
            return new Frustum().copy(this);
        }
        #endregion

    }
}
